import { IO } from "../io/io";
export namespace Tile_Struct
{
    export enum ColorBitLen
    {
        Bit1 = 1,//Mono
        Bit2 = 2,//=4color
        Bit4 = 4,//=16color
        Bit8 = 8,//=256color
    }
    export class TilePaletteData
    {
        bitLen: ColorBitLen = ColorBitLen.Bit1;//1bit =mono, 2bit nes= 4 color ,4bit =16color , 8bit =256color
        //调色板，仅用于 1bit 2bit 4bit 模式，长度固定，1bit时paletteMap长度=2，2bit=4，4bit=16，8bit=0
        //256color模式时palette 0长度
        palette: Uint8Array | null = null;//
        //调色板置换映射表，仅用于256color模式，Key:tileColorIndex=>Value:bankColorIndex
        //8bit 置换表
        paletteReplaceMap: { [id: number]: number } | null = null;//key,value,key,value,key,value,key,value,......
    }
    export class TileData
    {
        public pixelwidth: number = 1; //ushort pixelwidth; //取值1~256 ,不允许0
        public pixelheight: number = 1;//取值1~256 ,不允许0
        public bitLen: ColorBitLen = ColorBitLen.Bit1;//1bit =mono, 2bit nes= 4 color ,4bit =16color , 8bit =256color
        public tiledata: Uint8Array | null = null;

        public palette: TilePaletteData | null = null;

        public static Create(bit: ColorBitLen, pixelwidth: number, pixelheight: number): TileData
        {
            if (pixelheight == 0 || pixelwidth == 0 || pixelheight > 256 || pixelwidth > 256)
                throw new Error("error tile pixel size");
            let data = new TileData();
            data.bitLen = bit;
            data.pixelwidth = pixelwidth;
            data.pixelheight = pixelheight;
            let len = data.CalcPixelWidthAndPaletteSize();
            if (len.paletteLength > 0)
            {
                data.palette = new TilePaletteData();
                data.palette.bitLen = bit;
                data.palette.palette = new Uint8Array(len.paletteLength);
            }
            data.tiledata = new Uint8Array(len.byteWidth * pixelheight);
            return data;
        }
        private CalcPixelWidthAndPaletteSize(): { byteWidth: number, paletteLength: number }
        {
            let bytewidth = this.pixelwidth;
            let paletteLen = 0;
            if (this.bitLen == ColorBitLen.Bit1)
            {
                bytewidth = (this.pixelwidth / 8) | 0;
                if (this.pixelwidth % 8 != 0)
                    bytewidth++;
                paletteLen = 2;

            }
            else if (this.bitLen == ColorBitLen.Bit2)
            {
                bytewidth = (this.pixelwidth / 4) | 0;
                if (this.pixelwidth % 4 != 0)
                    bytewidth++;
                paletteLen = 4;

            }
            else if (this.bitLen == ColorBitLen.Bit4)
            {
                bytewidth = (this.pixelwidth / 2) | 0;
                if (this.pixelwidth % 2 != 0)
                    bytewidth++;
                paletteLen = 16;
            }
            else if (this.bitLen == ColorBitLen.Bit8)
            {
                bytewidth = (this.pixelwidth) | 0;
                paletteLen = 256;
            }
            else
            {
                throw new Error("error format.");
            }
            return { byteWidth: bytewidth, paletteLength: paletteLen };
        }
        public Read(stream: IO.binReader): void
        {
            this.bitLen = <ColorBitLen>stream.readByte();
            this.pixelwidth = (stream.readByte() + 1);
            this.pixelheight = (stream.readByte() + 1);
            let len = this.CalcPixelWidthAndPaletteSize();

            this.tiledata = new Uint8Array(len.byteWidth * this.pixelheight);

            stream.readBytes(this.tiledata, 0, len.byteWidth * this.pixelheight);

            //调色板
            if (len.paletteLength > 0)
            {
                this.palette = new TilePaletteData();
                this.palette.bitLen = this.bitLen;
                this.palette.palette = new Uint8Array(len.paletteLength);
                stream.readBytes(this.palette.palette, 0, len.paletteLength);
            }
            //只有256色有调色板置换
            if (this.bitLen == ColorBitLen.Bit8)
            {
                var maplen = stream.readByte();
                if (maplen > 0)
                {
                    if (this.palette == null)
                    {
                        this.palette = new TilePaletteData();
                        this.palette.bitLen = this.bitLen;
                        this.palette.palette = null;
                    }
                    var data = new Uint8Array(maplen * 2);
                    stream.readBytes(data, 0, maplen * 2);
                    this.palette.paletteReplaceMap = {};
                    for (var i = 0; i < maplen; i++)
                    {
                        this.palette.paletteReplaceMap[data[i * 2 + 0]] = data[i * 2 + 1];
                    }

                }
            }
        }
        public ToBytes(): Uint8Array
        {
            var ms = new IO.binWriter();
            this.Write(ms);
            return new Uint8Array(ms.getBuffer());
        }
        public Write(stream: IO.binWriter): void
        {
            stream.writeByte(this.bitLen);
            stream.writeByte(this.pixelwidth - 1);
            stream.writeByte(this.pixelheight - 1);

            let len = this.CalcPixelWidthAndPaletteSize();

            //数据区

            if (this.tiledata == null)
                throw new Error("error tiledata");

            stream.writeBytes(this.tiledata, 0, len.byteWidth * this.pixelheight);

            //调色板
            if (len.paletteLength > 0)
            {
                if (this.palette == null || this.palette.palette == null)
                    throw new Error("error palette ");
                stream.writeBytes(this.palette.palette, 0, len.paletteLength);
            }

            //只有256色有调色板置换
            if (this.bitLen == ColorBitLen.Bit8)
            {
                if (this.palette == null)
                    throw new Error("error palette ");
                let count = 0;
                if (this.palette.paletteReplaceMap != null)
                {
                    for (var i in this.palette.paletteReplaceMap)
                    {
                        count++;
                    }
                    //let count = this.palette.paletteReplaceMap != null ? this.palette.paletteReplaceMap.Count : 0;

                }

                if (count > 255)
                    throw new Error("too much palette");
                stream.writeByte(count);
                if (count > 0 && (this.palette.paletteReplaceMap != null))
                {

                    for (var key in this.palette.paletteReplaceMap)
                    {
                        var numkey = parseInt(key);
                        var numvalue = this.palette.paletteReplaceMap[numkey];
                        stream.writeByte(numkey);
                        stream.writeByte(numvalue);
                    }
                }
            }

        }


        public GetLine(line: number): Uint8Array
        {
            if (this.tiledata == null)
                throw Error("no tiledata");
            let bytesline = new Uint8Array(this.pixelwidth);
            if (this.bitLen == ColorBitLen.Bit1)
            {
                let bytewidth = (this.pixelwidth / 8) | 0;
                if (this.pixelwidth % 8 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 8)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 8];
                    if (x + 0 < this.pixelwidth) bytesline[x + 0] = (srcdata >> 7 & 0x1);
                    if (x + 1 < this.pixelwidth) bytesline[x + 1] = (srcdata >> 6 & 0x1);
                    if (x + 2 < this.pixelwidth) bytesline[x + 2] = (srcdata >> 5 & 0x1);
                    if (x + 3 < this.pixelwidth) bytesline[x + 3] = (srcdata >> 4 & 0x1);
                    if (x + 4 < this.pixelwidth) bytesline[x + 4] = (srcdata >> 3 & 0x1);
                    if (x + 5 < this.pixelwidth) bytesline[x + 5] = (srcdata >> 2 & 0x1);
                    if (x + 6 < this.pixelwidth) bytesline[x + 6] = (srcdata >> 1 & 0x1);
                    if (x + 7 < this.pixelwidth) bytesline[x + 7] = (srcdata >> 0 & 0x1);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit2)
            {
                let bytewidth = this.pixelwidth / 4;
                if (this.pixelwidth % 4 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 4)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 4];
                    bytesline[x + 0] = (srcdata >> 6 & 0x3);
                    bytesline[x + 1] = (srcdata >> 4 & 0x3);
                    bytesline[x + 2] = (srcdata >> 2 & 0x3);
                    bytesline[x + 3] = (srcdata >> 0 & 0x3);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit4)
            {
                let bytewidth = this.pixelwidth / 2;
                if (this.pixelwidth % 2 != 0)
                {
                    bytewidth++;
                }
                for (var x = 0; x < this.pixelwidth; x += 2)
                {
                    var srcdata = this.tiledata[line * bytewidth + x / 2];
                    bytesline[x + 0] = (srcdata >> 4 & 0x0f);
                    bytesline[x + 1] = (srcdata >> 0 & 0x0f);
                }
            }
            else if (this.bitLen == ColorBitLen.Bit8)
            {
                for (var x = 0; x < this.pixelwidth; x++)
                {
                    bytesline[x] = this.tiledata[line * this.pixelwidth + x];
                }
            }
            return bytesline;
        }

        public GetLineWithPalette(line: number, _palette: TilePaletteData | null = null): Uint8Array
        {
            if (_palette == null)
                _palette = this.palette;
            let bytesline = this.GetLine(line);
            if (this.bitLen != ColorBitLen.Bit8)
            {
                if (_palette != null && _palette.palette != null)
                    for (var i = 0; i < bytesline.length; i++)
                    {
                        bytesline[i] = _palette.palette[bytesline[i]];
                    }
            }
            else
            {
                if (_palette != null && _palette.paletteReplaceMap != null)
                {
                    for (var i = 0; i < bytesline.length; i++)
                    {
                        var v = _palette.paletteReplaceMap[bytesline[i]];
                        if (v != undefined)
                            bytesline[i] = v;
                    }
                }
            }
            return bytesline;
        }
        public SetLine(line: number, data: Uint8Array): void
        {
            let len = this.CalcPixelWidthAndPaletteSize();
            if (len.byteWidth > data.length)
                throw new Error("data is not enough.");
            if (this.bitLen == ColorBitLen.Bit1)
            {
                if (this.tiledata != null)
                    for (var x = 0; x < this.pixelwidth; x += 8)
                    {
                        let color = (
                            ((data[x + 0] & 1) << 7) |
                            ((data[x + 1] & 1) << 6) |
                            ((data[x + 2] & 1) << 5) |
                            ((data[x + 3] & 1) << 4) |
                            ((data[x + 4] & 1) << 3) |
                            ((data[x + 5] & 1) << 2) |
                            ((data[x + 6] & 1) << 1) |
                            ((data[x + 7] & 1) << 0)
                        ) & 0xff;
                        this.tiledata[line * len.byteWidth + x / 8] = color;
                    }
            }
            else if (this.bitLen == ColorBitLen.Bit2)
            {
                if (this.tiledata != null)
                    for (var x = 0; x < this.pixelwidth; x += 4)
                    {
                        let color = (
                            ((data[x + 0] & 3) << 6) |
                            ((data[x + 1] & 3) << 4) |
                            ((data[x + 2] & 3) << 2) |
                            ((data[x + 3] & 3) << 0)
                        ) & 0xff;
                        this.tiledata[line * len.byteWidth + x / 4] = color;
                    }
            }
            else if (this.bitLen == ColorBitLen.Bit4)
            {
                if (this.tiledata != null)
                    for (var x = 0; x < this.pixelwidth; x += 2)
                    {
                        let color = (
                            ((data[x + 0] & 0x0f) << 4) |
                            ((data[x + 1] & 0x0f) << 0)
                        ) & 0xff;
                        this.tiledata[line * len.byteWidth + x / 2] = color;
                    }
            }
            else if (this.bitLen == ColorBitLen.Bit8)
            {
                if (this.tiledata != null)
                    for (var x = 0; x < this.pixelwidth; x += 1)
                    {
                        let color = data[x];
                        this.tiledata[line * len.byteWidth + x] = color;
                    }
            }
        }
        public SetLineBool(line: number, data: boolean[]): void
        {
            if (this.bitLen == ColorBitLen.Bit1)
            {
                let bdata = new Uint8Array(data.length);
                for (var i = 0; i < bdata.length; i++)
                    bdata[i] = data[i] ? 1 : 0;
                this.SetLine(line, bdata);
            }
            else
            {
                throw new Error("not for this.");
            }
        }
    }
}